
#ifndef INCOMPRESSIBLEBACKEND_H_
#define INCOMPRESSIBLEBACKEND_H_

#include "DataStructures.h"
#include "IncompressibleFluid.h"
#include "AbstractState.h"
#include "Exceptions.h"

#include <vector>

namespace CoolProp {

class IncompressibleBackend : public AbstractState
{

   protected:
    /// Bulk values, state variables
    //double _T, _p; // From AbstractState
    std::vector<CoolPropDbl> _fractions;

    /// Reference values, no need to calculate them each time
    CachedElement _T_ref, _p_ref, _x_ref, _h_ref, _s_ref;
    CachedElement _hmass_ref, _smass_ref;

    /// Additional cached elements used for the partial derivatives
    CachedElement _cmass, _hmass, _rhomass, _smass, _umass;
    CachedElement _drhodTatPx, _dsdTatPx, _dhdTatPx, _dsdTatPxdT, _dhdTatPxdT, _dsdpatTx, _dhdpatTx;

    IncompressibleFluid* fluid;

    /// Set the fractions
    /**
    @param fractions The vector of fractions of the components converted to the correct input
    */
    void set_fractions(const std::vector<CoolPropDbl>& fractions);

   public:
    IncompressibleBackend();
    virtual ~IncompressibleBackend() {};
    std::string backend_name(void) {
        return get_backend_string(INCOMP_BACKEND);
    }

    /// The instantiator
    /// @param fluid object, mostly for testing purposes
    IncompressibleBackend(IncompressibleFluid* fluid);
    /// The instantiator
    /// @param fluid_name the string with the fluid name
    IncompressibleBackend(const std::string& fluid_name);
    /// The instantiator
    /// @param component_names The vector of strings of the fluid components, without file ending
    IncompressibleBackend(const std::vector<std::string>& component_names);

    // Incompressible backend uses different compositions
    bool using_mole_fractions(void) {
        return this->fluid->getxid() == IFRAC_MOLE;
    };
    bool using_mass_fractions(void) {
        return (this->fluid->getxid() == IFRAC_MASS || this->fluid->getxid() == IFRAC_PURE);
    };
    bool using_volu_fractions(void) {
        return this->fluid->getxid() == IFRAC_VOLUME;
    };

    /// Updating function for incompressible fluid
    /**
    In this function we take a pair of thermodynamic states, those defined in the input_pairs
    enumeration and update all the internal variables that we can.

    @param input_pair Integer key from CoolProp::input_pairs to the two inputs that will be passed to the function
    @param value1 First input value
    @param value2 Second input value
    */
    void update(CoolProp::input_pairs input_pair, double value1, double value2);

    std::string fluid_param_string(const std::string& ParamName) {
        if (!ParamName.compare("long_name")) {
            return calc_name();
        } else {
            throw ValueError(format("Input value [%s] is invalid.", ParamName.c_str()));
        }
    }

    /// Clear all the cached values
    bool clear();

    /// Update the reference values and clear the state
    void set_reference_state(double T0 = 20 + 273.15, double p0 = 101325, double x0 = 0.0, double h0 = 0.0, double s0 = 0.0);

    /// Set the mole fractions
    /**
    @param mole_fractions The vector of mole fractions of the components
    */
    void set_mole_fractions(const std::vector<CoolPropDbl>& mole_fractions);
    const std::vector<CoolPropDbl>& get_mole_fractions(void) {
        throw NotImplementedError("get_mole_fractions not implemented for this backend");
    };

    /// Set the mass fractions
    /**
    @param mass_fractions The vector of mass fractions of the components
    */
    void set_mass_fractions(const std::vector<CoolPropDbl>& mass_fractions);

    /// Set the volume fractions
    /**
    @param volu_fractions The vector of volume fractions of the components
    */
    void set_volu_fractions(const std::vector<CoolPropDbl>& volu_fractions);

    /// Check if the mole fractions have been set, etc.
    void check_status();

    /** We have to override some of the functions from the AbstractState.
	 *  The incompressibles are only mass-based and do not support conversion
	 *  from molar to specific quantities.
	 *  We also have a few new chaced variables that we need.
	 */
    /// Return the mass density in kg/m^3
    double rhomass(void);
    /// Return the mass enthalpy in J/kg
    double hmass(void);
    /// Return the molar entropy in J/mol/K
    double smass(void);
    /// Return the molar internal energy in J/mol
    double umass(void);
    /// Return the mass constant pressure specific heat in J/kg/K
    double cmass(void);

    double drhodTatPx(void);
    double dsdTatPx(void);
    double dhdTatPx(void);
    double dsdTatPxdT(void);
    double dhdTatPxdT(void);
    double dsdpatTx(void);
    double dhdpatTx(void);

    /// Return the temperature in K
    double T_ref(void);
    /// Return the pressure in Pa
    double p_ref(void);
    /// Return the composition
    double x_ref(void);
    /// Return the mass enthalpy in J/kg
    double h_ref(void);
    /// Return the molar entropy in J/mol/K
    double s_ref(void);

    /// Return the mass enthalpy in J/kg
    double hmass_ref(void);
    /// Return the molar entropy in J/mol/K
    double smass_ref(void);

    /** These functions should be protected, but that requires new tests.
     *  I'll leave that as a TODO item for now.
     */
    /// Calculate T given pressure and density
    /**
    @param rhomass The mass density in kg/m^3
    @param p The pressure in Pa
    @returns T The temperature in K
    */
    CoolPropDbl DmassP_flash(CoolPropDbl rhomass, CoolPropDbl p);
    /// Calculate T given pressure and enthalpy
    /**
    @param hmass The mass enthalpy in J/kg
    @param p The pressure in Pa
    @returns T The temperature in K
    */
    CoolPropDbl HmassP_flash(CoolPropDbl hmass, CoolPropDbl p);
    /// Calculate T given pressure and entropy
    /**
    @param smass The mass entropy in J/kg/K
    @param p The pressure in Pa
    @returns T The temperature in K
    */
    CoolPropDbl PSmass_flash(CoolPropDbl p, CoolPropDbl smass);

    //    /// Calculate T given pressure and internal energy
    //    /**
    //    @param umass The mass internal energy in J/kg
    //    @param p The pressure in Pa
    //    @returns T The temperature in K
    //    */
    //    CoolPropDbl PUmass_flash(CoolPropDbl p, CoolPropDbl umass);

    /// We start with the functions that do not need a reference state
    CoolPropDbl calc_rhomass(void) {
        return fluid->rho(_T, _p, _fractions[0]);
    };
    CoolPropDbl calc_cmass(void) {
        return fluid->c(_T, _p, _fractions[0]);
    };
    CoolPropDbl calc_cpmass(void) {
        return cmass();
    };
    CoolPropDbl calc_cvmass(void) {
        return cmass();
    };
    CoolPropDbl calc_viscosity(void) {
        return fluid->visc(_T, _p, _fractions[0]);
    };
    CoolPropDbl calc_conductivity(void) {
        return fluid->cond(_T, _p, _fractions[0]);
    };
    CoolPropDbl calc_T_freeze(void) {
        // No update is called - T_freeze is a trivial output
        fluid->checkX(_fractions[0]);
        return fluid->Tfreeze(_p, _fractions[0]);
    };
    CoolPropDbl calc_melting_line(int param, int given, CoolPropDbl value);
    CoolPropDbl calc_umass(void);

    /// ... and continue with the ones that depend on reference conditions.
    CoolPropDbl calc_hmass(void);
    CoolPropDbl calc_smass(void);

   public:
    /// Functions that can be used with the solver, they miss the reference values!
    CoolPropDbl raw_calc_hmass(double T, double p, double x);
    CoolPropDbl raw_calc_smass(double T, double p, double x);

   protected:
    /// Calculate the first partial derivative for the desired derivative
    CoolPropDbl calc_first_partial_deriv(parameters Of, parameters Wrt, parameters Constant);

    /* Below are direct calculations of the derivatives. Nothing
	 * special is going on, we simply use the polynomial class to
	 * derive the different functions with respect to temperature.
	 */
    /// Partial derivative of density with respect to temperature at constant pressure and composition
    double calc_drhodTatPx(double T, double p, double x) {
        return fluid->drhodTatPx(T, p, x);
    };
    /// Partial derivative of entropy with respect to temperature at constant pressure and composition
    double calc_dsdTatPx(double T, double p, double x) {
        return fluid->c(T, p, x) / T;
    };
    /// Partial derivative of enthalpy with respect to temperature at constant pressure and composition
    double calc_dhdTatPx(double T, double p, double x) {
        return fluid->c(T, p, x);
    };
    /// Partial derivative of entropy
    ///  with respect to temperature at constant pressure and composition
    ///  integrated in temperature
    double calc_dsdTatPxdT(double T, double p, double x) {
        return fluid->dsdTatPxdT(T, p, x);
    };
    /// Partial derivative of enthalpy
    ///  with respect to temperature at constant pressure and composition
    ///  integrated in temperature
    double calc_dhdTatPxdT(double T, double p, double x) {
        return fluid->dhdTatPxdT(T, p, x);
    };

    /* Other useful derivatives
	 */
    /// Partial derivative of entropy with respect to pressure at constant temperature and composition
    ///  \f[ \left( \frac{\partial s}{\partial p} \right)_T = - \left( \frac{\partial v}{\partial T} \right)_p = \rho^{-2} \left( \frac{\partial \rho}{\partial T} \right)_p \right) \f]
    double calc_dsdpatTx(double rho, double drhodTatPx);
    /// Partial derivative of enthalpy with respect to pressure at constant temperature and composition
    ///  \f[ \left( \frac{\partial h}{\partial p} \right)_T = v - T \left( \frac{\partial v}{\partial T} \right)_p = \rho^{-1} \left( 1 + T \rho^{-1} \left( \frac{\partial \rho}{\partial T} \right)_p \right) \f]
    double calc_dhdpatTx(double T, double rho, double drhodTatPx);

   public:
    /// Constants from the fluid object
    CoolPropDbl calc_Tmax(void) {
        return fluid->getTmax();
    };
    CoolPropDbl calc_Tmin(void) {
        return fluid->getTmin();
    };
    CoolPropDbl calc_fraction_min(void) {
        return fluid->getxmin();
    };
    CoolPropDbl calc_fraction_max(void) {
        return fluid->getxmax();
    };
    std::string calc_name(void) {
        return fluid->getName();
    };
    std::string calc_description(void) {
        return fluid->getDescription();
    };
};

} /* namespace CoolProp */
#endif /* INCOMPRESSIBLEBACKEND_H_ */
